<?php
namespace Shadon\CloudPrint\Gateways;

use Psr\Cache\CacheItemPoolInterface;
use Shadon\CloudPrint\Exceptions\Exception;
use Shadon\CloudPrint\Traits\HttpRequest;

/**
 * 映美云打印机
 * 
 * class JoLiMarkGateway
 * 
 * @author sunanzhi <sunanzhi@hotmail.com>
 */
class JoLiMarkGateway extends Gateway
{
    use HttpRequest;

    const APP_URL = 'http://mcp.jolimark.com/';
    // 获取访问令牌地址
    const GET_TOKEN = 'mcp/v2/sys/GetAccessToken'; 
    // 绑定打印机
    const BIND_PRINTER = 'mcp/v2/sys/BindPrinter'; 
    // 解绑打印机
    const UNBIND_PRINTER = 'mcp/v2/sys/UnBindPrinter'; 
    // 获得打印机状态
    const GET_PRINTER_STATUS = 'mcp/v2/sys/QueryPrinterStatus'; 
    // 获得打印机信息
    const QUERY_PRINTER_INFO = 'mcp/v2/sys/QueryPrinterInfo'; 
    // 取消未打印的任务
    const CANCEL_NOT_PRINT_ORDER = 'mcp/v2/sys/CancelNotPrintTask'; 
    // 查询未打印的任务
    const QUERY_NOT_PRINT_TASK = 'mcp/v3/sys/QueryNotPrintTask';
    // 查询打印任务状态
    const QUERY_PRINT_TASK_STATUS = 'mcp/v3/sys/QueryPrintTaskStatus';
    // 检查打印机绑定结果
    const CHECK_PRINTER_ENABLE_BIND = 'mcp/v3/sys/CheckPrinterEnableBind';
    // 查询打印机当前状态
    const QUERY_PRINTER_STATUS = 'mcp/v3/sys/QueryPrinterStatus';
    
    /**
     * 打印类型对应接口链接
     */
    const SEND_PRINT_URL = [
        'url' => 'mcp/v2/sys/PrintHtmlUrl', // 通过url进行抓去内容打印
        'httpCode' => 'mcp/v2/sys/PrintHtmlCode', //打印简单的html代码
        'urlImage' => 'mcp/v2/sys/PrintHtmlToPic', //打印复杂页面转图片
        'urlCode' => 'mcp/v2/sys/PrintRichHtmlCode', //打印复杂页面HTML代码
        'urlGray' => 'mcp/v2/sys/PrintHtmlToGrayPic', //打印复杂页面转灰度图
        'template' => 'mcp/v2/sys/PrintHtmlTemplate', //打印映美模版
        'pointText' => 'mcp/v2/sys/PrintPointText', //打印定点坐标
        'express' => 'mcp/v2/sys/PrintExpress', //打印快递单
        'escCode' => 'mcp/v2/sys/PrintEsc', //打印ESC指令
        'file' => 'mcp/v2/sys/PrintFile', //打印本地文件
        'fileUrl' => 'mcp/v2/sys/PrintFileByUrl', //打印远程文件
    ];

    /**
     * 打印机编号
     *
     * @var string
     */
    private $deviceId;

    /**
     * 签名类型
     *
     * @var string
     */
    private $signType = 'MD5';

    /**
     * 缓存炳
     *
     * @var CacheItemPoolInterface
     */
    private $cache = null;

    /**
     * 订单流水号
     *
     * @var string
     */
    private $cusOrderId;

    /**
     * 缓存过期时间
     *
     * @var integer
     */
    private $cacheExpire = 3600;

    /**
     * 获取token
     *
     * @return string
     */
    public function getAccessToken(): string
    {
        if(!empty($this->cache)){
            $cacheItem = $this->cache->getItem(md5(__CLASS__.$this->config['appId'].$this->config['appKey']));
            if (!$cacheItem->isHit()) {
                // 缓存没有命中
                $accessToken = $this->setAccessToken();
                $cacheItem->set($accessToken);
                $cacheItem->expiresAfter($this->cacheExpire);
                $this->cache->save($cacheItem);
            }else{
                $accessToken = $cacheItem->get();
            }
        }else{
            $accessToken = $this->setAccessToken();    
        }

        return $accessToken;
    }

    /**
     * 查询打印机基础信息
     *
     * @param string $deviceId 打印机编码
     * @return array
     */
    public function queryPrinterInfo(string $deviceId): array
    {
        if(empty($deviceId)){
            throw new Exception(Exception::EMPTY_DEVICE_ID);
        }
        $data['app_id'] = $this->config['appId'];
        $data['access_token'] = $this->getAccessToken();
        $data['device_id']  = $deviceId;
        $url = self::APP_URL.self::QUERY_PRINTER_INFO;
        $result = $this->handleResult($this->request('GET', $url, $data));

        return $result;
    }

    /**
     * 查询打印机当前状态
     *
     * @param string $deviceId
     * @return array
     */
    public function queryPrinterStatus(string $deviceId): array
    {
        if(empty($deviceId)){
            throw new Exception(Exception::EMPTY_DEVICE_ID);
        }
        $data['app_id'] = $this->config['appId'];
        $data['access_token'] = $this->getAccessToken();
        $data['device_id']  = $deviceId;
        $url = self::APP_URL.self::QUERY_PRINTER_STATUS;
        $result = $this->handleResult($this->request('GET', $url, $data));

        return $result;
    }

    /**
     * 绑定打印机
     *
     * @param string $deviceId 打印机编码
     * @param string $code 校准码
     * @return array
     */
    public function bindPrinter(string $deviceId, string $code): array
    {
        if(empty($deviceId) || empty($code)){
            throw new Exception(Exception::EMPTY_PARAMETER);
        }
        $data['app_id'] = $this->config['appId'];
        $data['access_token'] = $this->getAccessToken();
        $data['device_codes']  = $deviceId."#".$code;
        $url = self::APP_URL.self::BIND_PRINTER;
        $result = $this->handleResult($this->request('POST', $url, $data));

        return $result;
    }

    /**
     * 解绑打印机
     *
     * @param string $deviceId 打印机编码
     * @return boolean
     */
    public function unbindPrinter(string $deviceId): array
    {
        $data['app_id'] = $this->config['appId'];
        $data['access_token'] = $this->getAccessToken();
        $data['device_id']  = $deviceId;
        $url = self::APP_URL.self::UNBIND_PRINTER;
        $result = $this->handleResult($this->request('POST', $url, $data));
        
        return $result;
    }

    /**
     * 检查打印机绑定结果
     *
     * @param string $deviceId 打印机编号
     * @return array
     */
    public function checkPrinterEnableBind(string $deviceId):array
    {
        if(empty($deviceId)){
            throw new Exception(Exception::EMPTY_DEVICE_ID);
        }
        $data['app_id'] = $this->config['appId'];
        $data['access_token'] = $this->getAccessToken();
        $data['device_id'] = $deviceId;
        $result = $this->handleResult($this->request('POST', self::APP_URL.self::CHECK_PRINTER_ENABLE_BIND, $data));

        return $result;
    }

    /**
     * 查询打印任务状态
     *
     * @param string $curOrderId 客户系统订单流水号
     * @return array 
     */
    public function queryPrintTaskStatus(string $cusOrderId): array
    {
        if(empty($cusOrderId)){
            throw new Exception(Exception::EMPTY_CUS_ORDER_ID);
        }
        $data['app_id'] = $this->config['appId'];
        $data['access_token'] = $this->getAccessToken();
        $data['cus_orderid'] = $cusOrderId;
        $result = $this->handleResult($this->request('GET', self::APP_URL.self::QUERY_PRINT_TASK_STATUS, $data));
        
        return $result;
    }

    /**
     * 查询待打印任务状态
     *
     * @param string  $deviceId 打印机设备号
     * @return array
     */
    public function queryNotPrintTask(string $deviceId): array
    {
        if(empty($deviceId)){
            throw new Exception(Exception::EMPTY_DEVICE_ID);
        }
        $data['app_id'] = $this->config['appId'];
        $data['access_token'] = $this->getAccessToken();
        $data['device_id'] = $deviceId;
        $result = $this->handleResult($this->request('GET', self::APP_URL.self::QUERY_NOT_PRINT_TASK, $data));

        return $result;
    }

    /**
     * 取消待打印任务
     *
     * @return array
     */
    public function cancelNotPrintOrder(string $deviceId):array
    {
        $printerData['app_id'] = $this->config['appId'];
        $printerData['access_token'] = $this->getAccessToken();
        $printerData['device_id']  = $deviceId;
        $apiUrl = self::APP_URL.self::CANCEL_NOT_PRINT_ORDER;
        $result = $this->handleResult($this->request('POST', $apiUrl, $printerData));
        
        return $result;
    }

    /**
     * 发送打印数据
     *
     * @param string $category 类型
     * @param array $data 打印配置数据
     * @return array
     */
    public function sendPrint(string $deviceId, string $category, array $data): array
    {
        if(!isset(self::SEND_PRINT_URL[$category])){
            throw new Exception(Exception::EMPTY_PARAMETER);
        }
        $printerData['app_id'] = $this->config['appId'];
        $printerData['access_token'] = $this->getAccessToken();
        $printerData['device_ids']  = $deviceId;
        $printerData['cus_orderid'] = $this->cusOrderId = uniqid();
        $apiUrl = self::APP_URL.self::SEND_PRINT_URL[$category];

        switch($category){
            case 'url': // 通过url进行抓去内容打印
            $content = $data['url'];
            break;
            case 'httpCode': // 打印简单的html代码
            $content = file_get_contents($data['url']);
            break;
            case 'urlImage': // 打印复杂页面转图片
            $content = $data['url'];
            break;
            case 'urlCode': //打印复杂页面HTML代码
            $content = file_get_contents($data['url']);
            break;
            case 'urlGray': // 打印复杂页面转灰度图
            $content = $data['url'];
            break;
            case 'template': // 打印映美模版

            break;
            case 'pointText': // 打印定点坐标
            
            break;
            case 'express': // 打印快递单

            break;
            case 'escCode': // 打印ESC指令

            break;
            case 'file': // 打印本地文件

            break;
            case 'fileUrl': // 打印远程文件

            break;
            default:
            throw new Exception(Exception::CATEGORY_ERROR);
        }
        $printerData['bill_content'] = $content;
        $printerData['copies'] = 1;
        $result = $this->handleResult($this->request('POST', $apiUrl, $printerData));

        return $result;
    }

    /**
     * 设置缓存对象
     *
     * @param CacheItemPoolInterface $cache 缓存对象
     * @return self
     */
    public function setCache(CacheItemPoolInterface $cache)
    {
        $this->cache = $cache;

        return $this;
    }

    /**
     * 设置appId
     *
     * @param string $appId
     * @return self
     */
    public function setAppId(string $appId): self
    {
        $this->config['appId'] = $appId;

        return $this;
    }

    /**
     * 设置appKey
     *
     * @param string $appKey
     * @return self
     */
    public function setAppKey(string $appKey): self
    {
        $this->config['appKey'] = $appKey;

        return $this;
    }

    /**
     * 设置签名类型
     *
     * @param string $signType
     * @return self
     */
    public function setSignType(string $signType): self
    {
        $this->signType = $signType;

        return $this;
    }

    /**
     * 设置设备号
     *
     * @param string $deviceId
     * @return self
     */
    public function setDeviceId(string $deviceId): self
    {
        $this->deviceId = $deviceId;

        return $this;
    }

    /**
     * 设置缓存过期时间
     *
     * @param integer $expire
     * @return self
     */
    public function setCacheExpire(int $expire): self
    {
        $this->cacheExpire = $expire;

        return $this;
    }

    /**
     * 获取订单流水号
     *
     * @return string
     */
    public function getCusOrderId():string
    {
        return $this->cusOrderId;
    }

    /**
     * 设置accessToken
     *
     * @return void
     */
    private function setAccessToken()
    {
        $data['app_id'] = $this->config['appId'];
        $time = time();
        $data['time_stamp'] = $time;
        $data['sign_type'] = $this->signType;
        $string = "app_id={$data['app_id']}&sign_type={$this->signType}&time_stamp={$time}&key={$this->config['appKey']}";
        $data['sign'] = strtoupper(md5($string));
        $url = self::APP_URL.self::GET_TOKEN.'?'.http_build_query($data);
        $result = $this->handleResult($this->request('GET', $url));

        return $result['return_data']['access_token'];
    }

    /**
     * 刷新token
     *
     * @return void
     */
    private function refreshAccessToken()
    {
        if($this->cache){
            $cacheItem = $this->cache->getItem(md5(__CLASS__.$this->config['appId'].$this->config['appKey']));
            // 重新设置token缓存
            $accessToken = $this->setAccessToken();
            $cacheItem->set($accessToken);
            $cacheItem->expiresAfter($this->cacheExpire);
            $this->cache->save($cacheItem);
        }
    }

    /**
     * 处理结果
     *
     * @param array $result 结果数据
     * @return array
     */
    private function handleResult(array $result)
    {
        if($result['return_code'] != 0){
            // 异常
            switch($result['return_code']){
                case 10102: // token无效
                case 10103: // token过期
                $this->refreshAccessToken();
                break;
                // @todo other return_code
                default :
            }
            // 抛异常
            throw new \Exception($result['return_msg'], $result['return_code']);
        }

        return $result;
    }
}